/*
Copyright 2023.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/pointer"
	logconfigv1 "logconfig-opeator/api/v1"
	"logconfig-opeator/etcd"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// LogConfigReconciler reconciles a LogConfig object
type LogConfigReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=logconfig.com.bolingcavalry,resources=logconfigs,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=logconfig.com.bolingcavalry,resources=logconfigs/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=logconfig.com.bolingcavalry,resources=logconfigs/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the LogConfig object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile
func (r *LogConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	// TODO(user): your logic her
	log.Log.Info("controller running===========")
	instance := &logconfigv1.LogConfig{}
	err := r.Get(ctx, req.NamespacedName, instance)
	if err != nil {
		if errors.IsNotFound(err) {
			// if not create return nil, outside will not recall Reconcile
			log.Log.Info("controller running Error===========")
			return reconcile.Result{}, nil
		}
		log.Log.Error(err, "get controller error...........")
		return ctrl.Result{}, err
	}
	log.Log.Info("instance successful..........")
	// check logAgentDaemonSet if create
	logAgentDaemonSet := &appsv1.DaemonSet{}
	err = r.Get(ctx, req.NamespacedName, logAgentDaemonSet)
	if err != nil {
		if errors.IsNotFound(err) {
			log.Log.Info("logAgentDaemonSet is not exists..........")
			// create logAgentDaemonSet
			err = createLogAgentDaemonSetIfNotExists(ctx, r, instance, req)
			if err != nil {
				log.Log.Error(err, "create logAgentDaemonSet is error")
				return ctrl.Result{}, err
			}
		} else {
			log.Log.Error(err, "logAgentDaemonSet Error..........")
			return ctrl.Result{}, err
		}

	}
	// check transfer deployment if create
	deployment := &appsv1.Deployment{}
	err = r.Get(ctx, req.NamespacedName, deployment)
	if err != nil {
		if errors.IsNotFound(err) {
			log.Log.Info("transfer Deployment is not find")
			err := createTransFerDeploy(ctx, r, instance)
			if err != nil {
				log.Log.Error(err, "create transfer Deployment error.......")
				return ctrl.Result{}, err
			}
			return ctrl.Result{}, nil
		} else {
			log.Log.Error(err, "transfer deploy error")
			return ctrl.Result{}, err
		}
	}

	status := instance.Spec.Status
	log.Log.V(1).Info(status)
	if status != "del" {
		conf := instance.Spec.Conf
		collectEntry := etcd.CollectEntry{}
		slices := []etcd.CollectEntry{}
		for c := range conf {
			collectEntry.Topic = conf[c].Topic
			collectEntry.Path = conf[c].Path
			log.Log.Info(conf[c].Topic)
			log.Log.Info(conf[c].Path)
			slices = append(slices, collectEntry)
		}
		// update conf
		err := etcd.UpdateConf(slices)
		if err != nil {
			log.Log.Error(err, "create etcd UpdateConf fail.............")
		}
	}
	// check replicas
	replicas := *deployment.Spec.Replicas
	if replicas == instance.Spec.TransferSpec {
		log.Log.Info("instance.Spec.TransferSpec is same......")
		return ctrl.Result{}, nil
	}
	err = r.Update(ctx, deployment)
	if err != nil {
		log.Log.Error(err, "transfer deploy update error")
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *LogConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&logconfigv1.LogConfig{}).
		Complete(r)
}

// create LogAgentDaemonSet
func createLogAgentDaemonSetIfNotExists(ctx context.Context, r *LogConfigReconciler, logconfig *logconfigv1.LogConfig, req ctrl.Request) error {
	logger := log.Log.WithValues("func", "createLogAgentDaemonSet")
	// check LogAgentDaemonSet if exists
	daemonSet := &appsv1.DaemonSet{}
	err := r.Get(ctx, req.NamespacedName, daemonSet)
	if err == nil {
		logger.Info("LogAgentDaemonSet is exists............ ")
		return nil
	}
	// if err isn't NotFound ,return err
	if !errors.IsNotFound(err) {
		logger.Error(err, "get LogAgentDaemonSet error")
		return err
	}
	// create DaemonSet
	logger.Info("daemonSet creating ...............")
	daemonSet = &appsv1.DaemonSet{
		// DaemonSet nameSpace and Name
		ObjectMeta: metav1.ObjectMeta{
			Namespace: logconfig.Namespace,
			Name:      logconfig.Name,
		},
		// Selector: daemonset-logagent Template
		Spec: appsv1.DaemonSetSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"logagent": "daemonset-logagent",
				},
			},
			// Template
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"logagent": "daemonset-logagent",
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "daemonset-logagent",
							Image:           logconfig.Spec.LogAgentImage,
							ImagePullPolicy: "IfNotPresent",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									"cpu":    resource.MustParse("100m"),
									"memory": resource.MustParse("100Mi"),
								},
								Limits: corev1.ResourceList{
									"cpu":    resource.MustParse("500m"),
									"memory": resource.MustParse("500Mi"),
								},
							},
						},
					},
				},
			},
		},
	}
	// create connection ,if logconfig was deleted ,then logagent-daemonset will be deleted too
	logger.Info("set reference...............")
	err = controllerutil.SetControllerReference(logconfig, daemonSet, r.Scheme)
	if err != nil {
		logger.Error(err, "SetControllerReference error")
		return err
	}
	// create daemonSet
	logger.Info("start Creating LogAgentDaemonSet...................")
	err = r.Create(ctx, daemonSet)
	if err != nil {
		logger.Error(err, "create LogAgentDaemonSet error")
		return err
	}
	logger.Info("create LogAgentDaemonSet success!")
	return nil
}

// create deployment
func createTransFerDeploy(ctx context.Context, r *LogConfigReconciler, logconfig *logconfigv1.LogConfig) error {
	log := log.Log.WithValues("func", "createTransFerDeploy")
	// get Spec
	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: logconfig.Namespace,
			Name:      logconfig.Name,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: pointer.Int32(logconfig.Spec.TransferSpec),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"tansfer": "log-transfer-deploy",
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"tansfer": "log-transfer-deploy",
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "log-transfer-deploy",
							Image:           logconfig.Spec.TransferImage,
							ImagePullPolicy: "IfNotPresent",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									"cpu":    resource.MustParse("100m"),
									"memory": resource.MustParse("100Mi"),
								},
								Limits: corev1.ResourceList{
									"cpu":    resource.MustParse("200m"),
									"memory": resource.MustParse("200Mi"),
								},
							},
						},
					},
				},
			},
		},
	}
	log.Info("set transfer reference........")
	err := controllerutil.SetControllerReference(logconfig, deployment, r.Scheme)
	if err != nil {
		log.Error(err, "set transfer reference error .......")
		return err
	}
	// create transfer deploy
	log.Info("start create transfer Deployment..........")
	err = r.Create(ctx, deployment)
	if err != nil {
		log.Error(err, "create transfer Deployment error.........")
		return err
	}
	log.Info("create transfer deployment success.......")
	return nil
}
